package cn.thens.kdroid.core.log

import android.util.Log
import androidx.collection.LruCache
import cn.thens.kdroid.core.KDroid
import java.util.*

@Suppress("SpellCheckingInspection")
abstract class Logdog : LogInterface, PrettyIgnored {

    protected abstract val logger: ILogger

    protected abstract val tag: String

    override fun tag(tag: String): LogInterface {
        val self = this
        return object : Logdog() {
            override val tag: String = tag

            override val logger: ILogger = self.logger

            override fun isLoggable(priority: Int, tag: String): Boolean {
                return self.isLoggable(priority, tag)
            }
        }
    }

    override fun log(priority: Int, fn: () -> String): LogInterface {
        if (!isLoggable(priority, tag)) return this
        logger.log(priority, tag, fn())
        return this
    }

    private fun log(priority: Int, any: Any?): LogInterface {
        return log(priority) {
            when (any) {
                null -> "null"
                is String -> any
                is Throwable -> Log.getStackTraceString(any)
                is BooleanArray -> Arrays.toString(any)
                is ByteArray -> Arrays.toString(any)
                is CharArray -> Arrays.toString(any)
                is ShortArray -> Arrays.toString(any)
                is IntArray -> Arrays.toString(any)
                is LongArray -> Arrays.toString(any)
                is FloatArray -> Arrays.toString(any)
                is DoubleArray -> Arrays.toString(any)
                is Array<*> -> Arrays.deepToString(any)
                else -> any.toString()
            }
        }
    }

    override fun debug(any: Any?): LogInterface {
        return log(ILogger.DEBUG, any)
    }

    override fun warn(any: Any?): LogInterface {
        return log(ILogger.WARN, any)
    }

    override fun error(any: Any?): LogInterface {
        return log(ILogger.ERROR, any)
    }

    override fun wtf(sure: Boolean, message: String): LogInterface {
        if (sure) return this
        return log(ILogger.ASSERT) { message }
    }

    override fun time(name: String): LogInterface {
        return log(ILogger.DEBUG) {
            val currentTime = System.currentTimeMillis()
            val lastTime = timers[name] ?: currentTime
            timers.put(name, currentTime)
            "time($name): ${currentTime - lastTime}ms"
        }
    }

    override fun count(name: String): LogInterface {
        return log(ILogger.DEBUG) {
            val lastCount = counters[name] ?: 0L
            counters.put(name, lastCount + 1)
            "count($name): ${lastCount + 1}"
        }
    }

    override fun memory(): LogInterface {
        return log(ILogger.DEBUG) {
            val runtime = Runtime.getRuntime()
            """
                total memory: ${runtime.totalMemory()}
                free memory: ${runtime.freeMemory()}
                max memory: ${runtime.maxMemory()}
            """.trimIndent()
        }
    }

    override fun trace(): LogInterface {
        return log(ILogger.DEBUG) {
            val trace = Thread.currentThread().stackTrace
            val buffer = StringBuilder()
            var offset = ""
            for (e in trace) {
                val className = e.className.substring(e.className.lastIndexOf(".") + 1)
                if (offset.isNotEmpty()) buffer.append("\n")
                buffer.append(offset).append(className).append(".").append(e.methodName)
                        .append(" (").append(e.fileName).append(":").append(e.lineNumber).append(")")
                offset += "  "
            }
            buffer.toString()
        }
    }

    companion object : Logdog() {
        private const val LRU_CACHE_MAX_SIZE = 256
        private val timers = LruCache<String, Long>(LRU_CACHE_MAX_SIZE)
        private val counters = LruCache<String, Long>(LRU_CACHE_MAX_SIZE)

        override val tag: String = "@LOGDOG"

        private val loggers = mutableListOf<ILogger>(PrettyLogger(Loggers.logcat()))

        override val logger: ILogger by lazy {
            ILogger { priority, tag, message -> loggers.forEach { it.log(priority, tag, message) } }
        }

        override fun isLoggable(priority: Int, tag: String): Boolean {
            return KDroid.debug
        }

        fun addLogger(logger: ILogger): Logdog.Companion {
            loggers.add(logger)
            return this
        }

        fun removeLogger(logger: ILogger): Logdog.Companion {
            loggers.remove(logger)
            return this
        }
    }
}